import os
import json
from fastapi import FastAPI, File, UploadFile, Query, Form, Depends, HTTPException, Request
from fastapi.responses import FileResponse, RedirectResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware
from yolo_infer import YoloNoteDetector
from music_parse import parse_symbols_to_stream, stream_to_midi_file
from symbol_map import CLASS_NAMES
from audio_utils import midi_to_wav
import cv2
import numpy as np
import tempfile
from urllib.parse import urljoin
import logging
from typing import Optional

# 设置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

# 导入COS工具
import cos_utils

# 导入认证模块
import auth

# 导入聊天模块
import chat

# 导入数据库模块
import database

# 导入分析模块
import analyze

# 确保notifications模块可用
try:
    # 导入通知模块
    from api.notifications import router as notifications_router
    logger.info("成功导入通知模块")
except Exception as e:
    logger.error(f"导入通知模块失败: {str(e)}")
    import traceback
    logger.error(traceback.format_exc())

# 确保profile_routes模块可用
try:
    # 导入个人资料模块
    from profile_routes import router as profile_router
    logger.info("成功导入个人资料模块")
except Exception as e:
    logger.error(f"导入个人资料模块失败: {str(e)}")
    import traceback
    logger.error(traceback.format_exc())

# 确保community_routes模块可用
try:
    # 导入社区模块
    from community_routes import router as community_router
    logger.info("成功导入社区模块")
except Exception as e:
    logger.error(f"导入社区模块失败: {str(e)}")
    import traceback
    logger.error(traceback.format_exc())

# 设置Matplotlib和Ultralytics的配置目录
os.environ['MPLCONFIGDIR'] = '/tmp/matplotlib'
os.environ['YOLO_CONFIG_DIR'] = '/tmp/ultralytics'

# 确保配置目录存在
os.makedirs('/tmp/matplotlib', exist_ok=True)
os.makedirs('/tmp/ultralytics', exist_ok=True)

app = FastAPI(
    title="乐谱智识-音乐教育平台",
    description="乐谱OCR识别系统API",
    version="1.0.0",
    openapi_url="/openapi.json"
)

# 添加CORS中间件
app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://music.wushuang233.com", "http://localhost:8080"],  # 指定允许的前端域名
    allow_credentials=True,  # 设置为True以允许携带凭证的请求
    allow_methods=["*"],  # 允许所有方法
    allow_headers=["*"],  # 允许所有头部
)

# 路径配置
MODEL_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '../music_ocr_model/best.pt'))
TMP_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), 'tmp'))
os.makedirs(TMP_DIR, exist_ok=True)

# 挂载静态文件到/static
static_dir = os.path.join(os.path.dirname(__file__), 'static')
app.mount("/static", StaticFiles(directory=static_dir, html=True), name="static")

# 挂载tmp目录，使检测结果可以通过/tmp访问
app.mount("/tmp", StaticFiles(directory=TMP_DIR), name="tmp")

# 首页跳转到前端页面
@app.get("/")
def index():
    return RedirectResponse(url="/static/index.html")

# 查看检测结果图像
@app.get("/static/view_detection")
def view_detection(file: str = Query(...)):
    """提供检测结果图像的访问"""
    # 检查是否是完整URL
    if file.startswith("http"):
        return RedirectResponse(url=file)
    
    # 否则从本地文件系统提供
    image_path = os.path.join(TMP_DIR, file)
    print(f"请求图像路径: {image_path}, 文件存在: {os.path.exists(image_path)}")
    if os.path.exists(image_path):
        return FileResponse(image_path, media_type='image/jpeg')
    else:
        return {"error": f"图像不存在，路径: {image_path}"}

# 集成认证路由
app.include_router(auth.router, prefix="/auth")

# 集成聊天路由
app.include_router(chat.router, prefix="/chat")

# 集成社区路由 - 使用之前导入的community_router
try:
    app.include_router(community_router, prefix="/community")
    logger.info("成功注册社区模块路由")
except NameError:
    logger.error("社区模块路由未定义，无法注册")
except Exception as e:
    logger.error(f"注册社区模块路由时出错: {str(e)}")
    import traceback
    logger.error(traceback.format_exc())

# 集成个人资料路由 - 使用之前导入的profile_router
try:
    app.include_router(profile_router, prefix="/profile")
    logger.info("成功注册个人资料模块路由")
except NameError:
    logger.error("个人资料模块路由未定义，无法注册")
except Exception as e:
    logger.error(f"注册个人资料模块路由时出错: {str(e)}")
    import traceback
    logger.error(traceback.format_exc())

# 集成分析路由
app.include_router(analyze.router, prefix="/music")

# 集成通知路由
try:
    app.include_router(notifications_router, prefix="/notifications")
    logger.info("成功注册通知模块路由")
except NameError:
    logger.error("通知模块路由未定义，无法注册")
except Exception as e:
    logger.error(f"注册通知模块路由时出错: {str(e)}")
    import traceback
    logger.error(traceback.format_exc())

detector = YoloNoteDetector(MODEL_PATH)

# 定义可视化函数
def visualize_detections(image_path, symbols, output_path):
    """将检测结果可视化到图像上"""
    img = cv2.imread(image_path)
    if img is None:
        return False
    
    # 每个类别一种颜色
    np.random.seed(42)  # 固定随机种子以使颜色一致
    colors = {}
    
    # 在图像上绘制检测框和标签
    for sym in symbols:
        cls_name = sym['class']
        conf = sym['conf']
        bbox = sym['bbox']
        
        # 为该类别分配一个颜色
        if cls_name not in colors:
            colors[cls_name] = (
                np.random.randint(0, 255),
                np.random.randint(0, 255),
                np.random.randint(0, 255)
            )
        
        color = colors[cls_name]
        x1, y1, x2, y2 = [int(c) for c in bbox]
        
        # 绘制边界框
        cv2.rectangle(img, (x1, y1), (x2, y2), color, 2)
        
        # 准备标签文本
        label = f"{cls_name} {conf:.2f}"
        
        # 计算标签的大小
        (label_width, label_height), baseline = cv2.getTextSize(
            label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)
        
        # 绘制标签背景
        cv2.rectangle(
            img, 
            (x1, y1 - label_height - 5), 
            (x1 + label_width, y1), 
            color, 
            -1
        )
        
        # 绘制标签文本
        cv2.putText(
            img, 
            label, 
            (x1, y1 - 5), 
            cv2.FONT_HERSHEY_SIMPLEX, 
            0.5, 
            (255, 255, 255), 
            1
        )
    
    # 保存结果图像
    cv2.imwrite(output_path, img)
    return True

@app.post('/upload/')
async def upload_image(file: UploadFile = File(...), user_id: str = Form(None)):
    # 将字符串user_id转换为整数
    user_id_int = None
    logger.info(f"接收到的用户ID参数: {user_id}")
    
    if user_id:
        try:
            user_id_int = int(user_id)
            logger.info(f"成功转换用户ID为整数: {user_id_int}")
        except ValueError:
            logger.warning(f"无效的用户ID格式: {user_id}")
    else:
        logger.warning("未提供用户ID参数")
    
    # 创建临时目录用于处理文件
    with tempfile.TemporaryDirectory() as temp_dir:
        # 保存上传图片到临时目录
        filename = file.filename or "uploaded_image.jpg"  # 提供默认文件名
        local_img_path = os.path.join(temp_dir, filename)
        with open(local_img_path, 'wb') as f:
            f.write(await file.read())
        
        # 为云存储生成唯一文件名
        import uuid
        import time
        file_prefix = f"{int(time.time())}_{uuid.uuid4().hex[:8]}"
        cloud_filename = f"{file_prefix}_{filename}"
        
        # 检测
        symbols = detector.detect(local_img_path)
        logger.info(f"识别结果: 检测到 {len(symbols)} 个符号")
        # for i, sym in enumerate(symbols):
        #     logger.info(f"  {i+1}. {sym['class']} (置信度: {sym['conf']:.2f})")
        
        # 将检测结果保存为JSON
        detection_json = os.path.join(temp_dir, f"{file_prefix}_detections.json")
        detection_json_content = json.dumps(symbols, indent=2)
        with open(detection_json, 'w') as f:
            f.write(detection_json_content)
        
        # 可视化检测结果
        vis_path = os.path.join(temp_dir, f"{file_prefix}_vis.jpg")
        visualize_result = visualize_detections(local_img_path, symbols, vis_path)
        logger.info(f"可视化结果保存在: {vis_path}, 成功: {visualize_result}")
        
        try:
            # 解析为音符流
            s = parse_symbols_to_stream(symbols)
            
            # 生成MIDI
            midi_path = os.path.join(temp_dir, f"{file_prefix}.mid")
            stream_to_midi_file(s, midi_path)
            
            # 转WAV
            wav_path = os.path.join(temp_dir, f"{file_prefix}.wav")
            midi_to_wav(midi_path, wav_path)
            
            # 确保音频文件存在
            if not os.path.exists(wav_path) or os.path.getsize(wav_path) == 0:
                raise Exception("WAV文件生成失败或为空")
            
            # 上传文件到腾讯云COS
            cloud_paths = {}
            
            # 上传原始图片
            img_cloud_path = f"uploads/images/{cloud_filename}"
            img_url = cos_utils.upload_file(local_img_path, img_cloud_path)
            cloud_paths['original_image'] = img_url
            
            # 上传检测JSON
            json_cloud_path = f"uploads/json/{file_prefix}_detections.json"
            json_url = cos_utils.upload_file(detection_json, json_cloud_path)
            cloud_paths['detection_json'] = json_url
            
            # 上传可视化结果
            vis_url = None
            if visualize_result:
                vis_cloud_path = f"uploads/visualizations/{file_prefix}_vis.jpg"
                vis_url = cos_utils.upload_file(vis_path, vis_cloud_path)
                cloud_paths['visualization'] = vis_url
            
            # 上传MIDI
            midi_cloud_path = f"uploads/midi/{file_prefix}.mid"
            midi_url = cos_utils.upload_file(midi_path, midi_cloud_path)
            cloud_paths['midi'] = midi_url
            
            # 上传WAV
            wav_cloud_path = f"uploads/audio/{file_prefix}.wav"
            wav_url = cos_utils.upload_file(wav_path, wav_cloud_path)
            cloud_paths['audio'] = wav_url
            
            # 记录到数据库(如果提供了用户ID)
            score_upload_id = None
            if user_id_int:
                logger.info(f"准备将上传记录保存到数据库，用户ID: {user_id_int}")
                
                # 获取文件大小
                file_size = os.path.getsize(local_img_path)
                
                # 记录乐谱上传
                try:
                    score_upload_id = database.record_score_upload(
                        user_id=user_id_int,
                        original_filename=filename,
                        stored_filename=cloud_filename,
                        file_size=file_size,
                        file_type=os.path.splitext(filename)[1],
                        detection_json=detection_json_content,
                        original_image_url=img_url,
                        visualization_url=vis_url,
                        json_url=json_url
                    )
                    logger.info(f"成功记录乐谱上传，ID: {score_upload_id}")
                except Exception as db_error:
                    logger.error(f"记录乐谱上传时出错: {str(db_error)}")
                
                # 记录音频文件
                if score_upload_id:
                    # 获取音频时长
                    duration = None
                    # TODO: 实现获取音频时长的功能
                    
                    try:
                        audio_id = database.record_audio_files(
                            score_upload_id=score_upload_id,
                            midi_filename=os.path.basename(midi_path),
                            wav_filename=os.path.basename(wav_path),
                            midi_url=midi_url,
                            wav_url=wav_url,
                            duration=duration
                        )
                        logger.info(f"成功记录音频文件，ID: {audio_id}")
                    except Exception as db_error:
                        logger.error(f"记录音频文件时出错: {str(db_error)}")
                    
                    # 计算统计数据
                    note_count = sum(1 for sym in symbols if sym['class'].startswith('note'))
                    rest_count = sum(1 for sym in symbols if sym['class'].startswith('rest'))
                    
                    # 记录统计数据
                    try:
                        stats_id = database.record_detection_stats(
                            score_upload_id=score_upload_id,
                            total_symbols=len(symbols),
                            note_count=note_count,
                            rest_count=rest_count,
                            # 其他字段可根据实际情况填写
                        )
                        logger.info(f"成功记录检测统计数据，ID: {stats_id}")
                    except Exception as db_error:
                        logger.error(f"记录检测统计数据时出错: {str(db_error)}")
            else:
                logger.warning("未提供用户ID，跳过数据库记录")
            
            # 返回所有URL和音频文件的重定向
            response_data = {
                "success": True,
                "message": "文件处理成功并上传到云存储",
                "files": cloud_paths,
                "audio_url": wav_url,
                "visualization_url": cloud_paths.get('visualization'),
                "symbols": symbols  # 添加检测到的符号数据
            }
            
            if score_upload_id:
                logger.info(f"将上传ID添加到响应: {score_upload_id}")
                response_data["upload_id"] = score_upload_id
            else:
                logger.warning("没有上传ID可以添加到响应")
                
            logger.info(f"返回响应: {response_data}")
            return response_data
            
        except Exception as e:
            logger.error(f"生成或返回音频时出错: {str(e)}")
            
            # 创建一个简单的备用音频
            try:
                # 使用scipy生成一个简单的提示音
                import numpy as np
                from scipy.io import wavfile
                
                fallback_wav = os.path.join(temp_dir, f"{file_prefix}_error.wav")
                sample_rate = 44100
                t = np.linspace(0, 1, sample_rate, endpoint=False)
                audio = (32767 * 0.5 * np.sin(2 * np.pi * 440 * t)).astype(np.int16)
                wavfile.write(fallback_wav, sample_rate, audio)
                
                # 上传错误音频
                error_cloud_path = f"uploads/errors/{file_prefix}_error.wav"
                error_url = cos_utils.upload_file(fallback_wav, error_cloud_path)
                
                return {
                    "success": False,
                    "message": f"音频生成失败: {str(e)}",
                    "error_audio_url": error_url
                }
            except Exception as fallback_error:
                return JSONResponse(
                    status_code=500,
                    content={
                        "success": False,
                        "message": f"处理完全失败: {str(e)}，备用音频生成也失败: {str(fallback_error)}"
                    }
                )

# 添加用于直接启动服务器的代码
if __name__ == "__main__":
    import uvicorn
    
    # 在开发环境中启动服务器
    uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True) 